﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Linq.Expressions;

namespace CommonDevelop
{
    public enum EnumNodeType
    {
        BinaryOperator = 1,
        UndryOperator = 2,
        Constant = 3,
        MemberAccess = 4,
        Call = 5,
        Add = 6,
        Not = 7,
        Unknown = -99,
        NotSupported = -98
    }

    /// <summary>
    /// 注意：对于!(!IsDelete)这种写法是不允许的
    /// </summary>
    public static class ExpressionHelper
    {
        public static string GetDescription<T>(this T enumitem)
        {
            var item = enumitem.GetType().GetField(enumitem.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), true);
            var result = ((DescriptionAttribute)item[0]).Description;
            return result;
        }

        /// <summary>
        /// 获取一元表达式的成员名称
        /// </summary>
        public static string GetUnaryMemberName(Expression func)
        {
            if (func != null)
                if (CheckExpressionType(func) == EnumNodeType.UndryOperator)
                {
                    UnaryExpression UE = func as UnaryExpression;
                    if (UE != null)
                        if (CheckExpressionType(UE.Operand) == EnumNodeType.MemberAccess)
                        {
                            MemberExpression ME = UE.Operand as MemberExpression;
                            return ME.Member.Name;
                        }
                }

            return string.Empty;
        }

        public static string GetSqlByExpression(Expression func)
        {
            var funcType = CheckExpressionType(func);
            switch (funcType)
            {
                case EnumNodeType.Add:
                    return FormatSqlExpression(VisitBinaryExpression(func as BinaryExpression));
                case EnumNodeType.BinaryOperator:
                    return FormatSqlExpression(VisitBinaryExpression(func as BinaryExpression));
                case EnumNodeType.Constant:
                    return FormatSqlExpression(VisitConstantExpression(func as ConstantExpression));
                case EnumNodeType.Call:
                    return FormatSqlExpression(VisitMethodCallExpression(func as MethodCallExpression));
                case EnumNodeType.Not:
                    return FormatSqlExpression(VisitUnaryExpressionNot(func as UnaryExpression));
                case EnumNodeType.UndryOperator:
                    return FormatSqlExpression(VisitUnaryExpression(func as UnaryExpression));
                case EnumNodeType.MemberAccess:
                    return FormatSqlExpression(VisitMemberAccessExpressionBool(func as MemberExpression));
                default:
                    throw new NotSupportedException("不支持的操作在表达式处理中：" + funcType.GetDescription());
            }
        }

        #region--表达式转译code--

        private static string FormatSqlExpression(string sql)
        {
            return sql.Replace(" )", ")");
        }

        private static string ExpressionTypeToString(ExpressionType func)
        {
            switch (func)
            {
                case ExpressionType.Add: return "+ ";
                case ExpressionType.AndAlso: return "AND ";
                case ExpressionType.OrElse: return "OR ";
                case ExpressionType.Equal: return "= ";
                case ExpressionType.NotEqual: return "!= ";
                case ExpressionType.GreaterThan: return "> ";
                case ExpressionType.GreaterThanOrEqual: return ">= ";
                case ExpressionType.LessThan: return "< ";
                case ExpressionType.LessThanOrEqual: return "<= ";
                case ExpressionType.Not: return "<> "; //"NOT ";
                case ExpressionType.Convert: return "";
                default: return "unknown";
            }
        }

        private static EnumNodeType CheckExpressionType(Expression func)
        {
            switch (func.NodeType)
            {
                case ExpressionType.Add:
                    return EnumNodeType.Add;
                case ExpressionType.AndAlso:
                case ExpressionType.OrElse:
                case ExpressionType.Equal:
                    return EnumNodeType.BinaryOperator;
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.LessThan:
                case ExpressionType.NotEqual:
                    return EnumNodeType.BinaryOperator;
                case ExpressionType.Constant:
                    return EnumNodeType.Constant;
                case ExpressionType.MemberAccess:
                    return EnumNodeType.MemberAccess;
                case ExpressionType.Call:
                    return EnumNodeType.Call;
                case ExpressionType.Not:
                    return EnumNodeType.Not;
                case ExpressionType.Convert:
                    return EnumNodeType.UndryOperator;
                default:
                    return EnumNodeType.Unknown;
            }
        }

        private static string VisitUnaryExpressionNot(UnaryExpression func)
        {
            var result = ExpressionTypeToString(func.NodeType);
            var funcType = CheckExpressionType(func.Operand);
            string exp = null;
            switch (funcType)
            {
                case EnumNodeType.Add:
                    exp = VisitBinaryExpression(func.Operand as BinaryExpression);
                    break;
                case EnumNodeType.BinaryOperator:
                    exp = VisitBinaryExpression(func.Operand as BinaryExpression);
                    break;
                case EnumNodeType.Constant:
                    exp = VisitConstantExpression(func.Operand as ConstantExpression);
                    break;
                case EnumNodeType.Call:
                    exp = VisitMethodCallExpression(func.Operand as MethodCallExpression);
                    break;
                case EnumNodeType.UndryOperator:
                    exp = VisitUnaryExpression(func.Operand as UnaryExpression);
                    break;
                case EnumNodeType.MemberAccess:
                    exp = VisitMemberAccessExpression(func.Operand as MemberExpression);
                    break;
                default:
                    throw new NotSupportedException("不支持的操作在一元操作处理中：" + funcType.GetDescription());
            }

            if (func.NodeType == ExpressionType.Not)
                return "(" + exp + " " + result + "1)";
            else
                return result + exp;
        }

        private static string VisitUnaryExpression(UnaryExpression func)
        {
            var result = ExpressionTypeToString(func.NodeType);
            var funcType = CheckExpressionType(func.Operand);
            switch (funcType)
            {
                case EnumNodeType.Add:
                    return result + VisitBinaryExpression(func.Operand as BinaryExpression);
                case EnumNodeType.BinaryOperator:
                    return result + VisitBinaryExpression(func.Operand as BinaryExpression);
                case EnumNodeType.Constant:
                    return result + VisitConstantExpression(func.Operand as ConstantExpression);
                case EnumNodeType.Call:
                    return result + VisitMethodCallExpression(func.Operand as MethodCallExpression);
                case EnumNodeType.UndryOperator:
                    return result + VisitUnaryExpression(func.Operand as UnaryExpression);
                case EnumNodeType.Not:
                    return result += VisitUnaryExpressionNot(func.Operand as UnaryExpression);
                case EnumNodeType.MemberAccess:
                    return result + VisitMemberAccessExpression(func.Operand as MemberExpression);
                default:
                    throw new NotSupportedException("不支持的操作在一元操作处理中：" + funcType.GetDescription());
            }
        }

        private static string VisitConstantExpression(ConstantExpression func)
        {
            if (func.Value.ToString() == "")
            {
                return "\'\' ";
            }
            else if (func.Value.ToString() == "True")
            {
                return "1 ";
            }
            else if (func.Value.ToString() == "False")
            {
                return "0 ";
            }
            else
            {
                return "'" + func.Value.ToString() + "' ";
            }
        }

        private static string VisitMemberAccessExpressionBool(MemberExpression func)
        {
            var tablename = SugarBizDB.GetTableName(func.Expression.Type);
            var NodeType = func.Expression.NodeType;

            string ParameterStr = null;
            if (!string.IsNullOrEmpty(tablename))
            {
                if (NodeType == ExpressionType.Parameter)
                    ParameterStr = tablename + "." + func.Member.Name + " ";

                if (!string.IsNullOrEmpty(ParameterStr))
                    if (func.Type == typeof(bool))
                        return ParameterStr + " = 1 ";
            }

            return VisitMemberAccessExpression(func);
        }
        /// <summary>
        /// 判断包含变量的表达式
        /// </summary>
        private static string VisitMemberAccessExpression(MemberExpression func)
        {
            var tablename = SugarBizDB.GetTableName(func.Expression.Type);
            var NodeType = func.Expression.NodeType;

            if (!string.IsNullOrEmpty(tablename))
            {
                if (NodeType == ExpressionType.Parameter)
                    return tablename + "." + func.Member.Name + " ";
            }
            object value;
            switch (func.Type.Name)
            {
                case "Int32":
                    {
                        var getter = Expression.Lambda<Func<int>>(func).Compile();
                        value = getter();
                    }
                    break;
                case "Int64":
                    {
                        var getter = Expression.Lambda<Func<long>>(func).Compile();
                        value = getter();
                    }
                    break;
                case "Decimal":
                    {
                        var getter = Expression.Lambda<Func<decimal>>(func).Compile();
                        value = getter();
                    }
                    break;
                case "Single":
                    {
                        var getter = Expression.Lambda<Func<float>>(func).Compile();
                        value = getter();
                    }
                    break;
                case "Double":
                    {
                        var getter = Expression.Lambda<Func<double>>(func).Compile();
                        value = getter();
                    }
                    break;
                case "String":
                    {
                        var getter = Expression.Lambda<Func<string>>(func).Compile();
                        value = "'" + getter() + "'";
                    }
                    break;
                case "DateTime":
                    {
                        var getter = Expression.Lambda<Func<DateTime>>(func).Compile();
                        value = "'" + getter() + "'";
                    }
                    break;
                default:
                    {
                        var getter = Expression.Lambda<Func<object>>(func).Compile();
                        value = getter();
                    }
                    break;
            }
            return value.ToString();
        }

        private static string VisitMethodCallExpression(MethodCallExpression func)
        {
            if (func.Method.Name.Contains("Contains"))
            {
                if (func.Object.Type.GetInterface(typeof(IList).Name) != null)
                {
                    var getter = Expression.Lambda<Func<object>>(func.Object).Compile();
                    var data = getter() as IEnumerable;
                    var caller = func.Arguments[0];
                    while (caller.NodeType == ExpressionType.Call)
                    {
                        caller = (caller as MethodCallExpression).Object;
                    }
                    var field = VisitMemberAccessExpression(caller as MemberExpression);
                    var list = (from object i in data select "'" + i + "'").ToList();
                    if (list.Count < 1)
                        return field + " <> " + field;

                    return field + " IN (" + string.Join(",", list.Cast<string>().ToArray()) + ") ";
                }
                else
                {
                    var field = VisitMemberAccessExpression(func.Object as MemberExpression);
                    var caller = func.Arguments[0];
                    string val = GetConstantVal(caller);

                    if (string.IsNullOrEmpty(val))
                        return field;
                    else
                    {
                        val = val.Replace("'", "");
                        return field + " LIKE '%" + val + "%' ";
                    }
                }
            }
            else if (func.Method.Name.Contains("Equals"))
            {
                var field = VisitMemberAccessExpression(func.Object as MemberExpression);

                var caller = func.Arguments[0];
                string val = GetConstantVal(caller);

                if (string.IsNullOrEmpty(val))
                    return field;
                else
                    return field + " = " + val;
            }
            else
            {
                throw new NotSupportedException("VisitMethodCallExpression");
            }
        }

        private static string GetConstantVal(Expression caller)
        {
            if (caller == null)
                return null;

            string val = "";
            if (caller.NodeType == ExpressionType.Constant && caller.Type == typeof(string))
                val = caller.ToString().Replace("\"", "");
            else if (caller.NodeType == ExpressionType.MemberAccess)
            {
                object value;
                var func = caller as MemberExpression;
                if (func == null)
                    return null;

                switch (func.Type.Name)
                {
                    case "Int32":
                        {
                            var getter = Expression.Lambda<Func<int>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Int64":
                        {
                            var getter = Expression.Lambda<Func<long>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Decimal":
                        {
                            var getter = Expression.Lambda<Func<decimal>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Single":
                        {
                            var getter = Expression.Lambda<Func<float>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Double":
                        {
                            var getter = Expression.Lambda<Func<double>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "String":
                        {
                            var getter = Expression.Lambda<Func<string>>(func).Compile();
                            value = "'" + getter() + "'";
                        }
                        break;
                    case "DateTime":
                        {
                            var getter = Expression.Lambda<Func<DateTime>>(func).Compile();
                            value = "'" + getter() + "'";
                        }
                        break;
                    default:
                        {
                            var getter = Expression.Lambda<Func<object>>(func).Compile();
                            value = getter();
                        }
                        break;
                }
                val = value.ToString();
            }
            else
            {
                object value;
                var func = caller as BinaryExpression;
                if (func == null)
                    return null;

                switch (func.Type.Name)
                {
                    case "Int32":
                        {
                            var getter = Expression.Lambda<Func<int>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Int64":
                        {
                            var getter = Expression.Lambda<Func<long>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Decimal":
                        {
                            var getter = Expression.Lambda<Func<decimal>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Single":
                        {
                            var getter = Expression.Lambda<Func<float>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "Double":
                        {
                            var getter = Expression.Lambda<Func<double>>(func).Compile();
                            value = getter();
                        }
                        break;
                    case "String":
                        {
                            var getter = Expression.Lambda<Func<string>>(func).Compile();
                            value = "'" + getter() + "'";
                        }
                        break;
                    case "DateTime":
                        {
                            var getter = Expression.Lambda<Func<DateTime>>(func).Compile();
                            value = "'" + getter() + "'";
                        }
                        break;
                    default:
                        {
                            var getter = Expression.Lambda<Func<object>>(func).Compile();
                            value = getter();
                        }
                        break;
                }
                val = value.ToString();
            }

            return val;
        }

        private static string VisitBinaryExpression(BinaryExpression func)
        {
            var result = "(";
            var leftType = CheckExpressionType(func.Left);
            switch (leftType)
            {
                case EnumNodeType.BinaryOperator:
                    result += VisitBinaryExpression(func.Left as BinaryExpression); break;
                case EnumNodeType.Constant:
                    result += VisitConstantExpression(func.Left as ConstantExpression); break;
                case EnumNodeType.MemberAccess:
                    result += VisitMemberAccessExpressionBool(func.Left as MemberExpression); break;
                case EnumNodeType.UndryOperator:
                    result += VisitUnaryExpression(func.Left as UnaryExpression); break;
                case EnumNodeType.Call:
                    result += VisitMethodCallExpression(func.Left as MethodCallExpression); break;
                case EnumNodeType.Not:
                    result += VisitUnaryExpressionNot(func.Left as UnaryExpression); break;
                default:
                    throw new NotSupportedException("不支持的操作在二元操作处理中:" + leftType.GetDescription());
            }

            result += " " + ExpressionTypeToString(func.NodeType) + " ";
            var rightType = CheckExpressionType(func.Right);
            switch (rightType)
            {
                case EnumNodeType.BinaryOperator:
                    result += VisitBinaryExpression(func.Right as BinaryExpression); break;
                case EnumNodeType.Constant:
                    var E_Val = VisitConstantExpression(func.Right as ConstantExpression);
                    if (func.Left.Type == typeof(bool))
                    {
                        if (E_Val.Trim() == "1" || E_Val.Trim() == "0")
                        {
                            result = result.Replace("1", E_Val.Trim()).Trim();
                            if (RepeatStr(result, "="))
                                result = result.Substring(0, result.LastIndexOf("=")).Trim();
                        }
                        else
                            result += E_Val;
                    }
                    else
                        result += E_Val;

                    break;
                case EnumNodeType.MemberAccess:
                    result += VisitMemberAccessExpressionBool(func.Right as MemberExpression); break;
                case EnumNodeType.UndryOperator:
                    result += VisitUnaryExpression(func.Right as UnaryExpression); break;
                case EnumNodeType.Call:
                    result += VisitMethodCallExpression(func.Right as MethodCallExpression); break;
                case EnumNodeType.Not:
                    result += VisitUnaryExpressionNot(func.Right as UnaryExpression); break;
                default:
                    throw new NotSupportedException("不支持的操作在二元操作处理中:" + rightType.GetDescription());
            }
            result += ") ";
            return result;
        }

        private static bool RepeatStr(string str, string key)
        {
            if (string.IsNullOrEmpty(str))
                return false;

            if (string.IsNullOrEmpty(key))
                return false;

            if (str.LastIndexOf(key) > 0)
            {
                var Temp = str.Substring(0, str.LastIndexOf(key));
                if (Temp.LastIndexOf(key) > 0)
                    return true;
            }

            return false;
        }

        #endregion

    }
}
